#region Licence
// Copyright (c) 2008 Tasman Logic Ltd

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies 
// or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;

namespace ActiveSharp.PropertyMapping
{

    /// <summary>
    /// Maps offsets (positions within the storage of an object) to fields
    /// </summary>
    /// <remarks>It works by constructing a dummy instance of the target class, then generating a dynamic method
    /// which gets the field offset for every field in the object</remarks>
    public class OffsetToFieldMap
    {
        public OffsetToFieldMap(Type typeToMap)
        {
            if (typeToMap.IsAbstract)
                throw new ArgumentException("Cannot map offsets for abstract class");  // CAN map for things DERIVED from abstract classes, just not for the abstract class directly

            // Work our way from the bottom to the top of the hierarchy, mapping offsets for the fields declared in each type
            for (Type t = typeToMap; t != typeof(object); t = t.BaseType)
            {
                Action<Dictionary<int, string>> populateMap = GenerateMapperMethod(t, typeToMap);
                populateMap(_map);
            }
        }

        readonly Dictionary<int, string> _map = new Dictionary<int, string>();

        internal int MaxOffset
        {
            get
            {
                int result = 0;
                foreach (int off in _map.Keys)
                    if (off > result)
                        result = off;
                return result;
            }
        }

        internal string GetFieldOrNull(int offset)
        {
            string result;
            if (_map.TryGetValue(offset, out result))
                return result;
            else
                return null;
        }

        /// <summary>
        /// Returns the name of the field that corresponds to the given offset
        /// </summary>
        /// <returns>Name of field as a string. (Note that we use strings, not <see cref="FieldInfo"/>s, in an attempt to keep the implementation as simple as possible</returns>
        public string this[int offset]
        {
            get { return _map[offset]; }
        }

        // todo: add generic constraint on target type (somewhere, e.g. in outermost PropertyMap API) to ensure the target type has a parameterless constructor

        /// <summary>
        /// Generates a method to find mappings from offsets to fields for all fields declared in <paramref name="currentType"/>
        /// </summary>
        /// <remarks>We do declared fields only because ancestor ones will typically be private, so we couldn't see them 
        /// via reflection here anyway.  Instead, our caller calls us multiple times to walk up the hierarchy</remarks>
        Action<Dictionary<int, string>> GenerateMapperMethod(Type currentType, Type mostDerivedKnownType)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(
                "PopulateMap", 
                typeof(void), 
                new Type[] {typeof(Dictionary<int, string>)}, 
                currentType, 
                false);

            ILGenerator gen = dynamicMethod.GetILGenerator();

            // Following code generated with the assistance of the Reflection.Emit Language Plug-in for Reflector

            // Preparing Reflection instances
            MethodInfo getTypeFromHandleMethod = typeof(Type).GetMethod(
                                                    "GetTypeFromHandle",
                                                    BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
                                                    null,
                                                    new Type[]{typeof(RuntimeTypeHandle)},
                                                    null);

            MethodInfo getUninitializedObjectMethod = typeof(FormatterServices).GetMethod(
                                                         "GetSafeUninitializedObject",
                                                         BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
                                                         null,
                                                         new Type[]{typeof(Type)},
                                                         null);


            MethodInfo offsetMethod = typeof(OffsetUtil).GetMethod("GetOffset", BindingFlags.Static | BindingFlags.Public);

            MethodInfo addMethod = typeof(Dictionary<int, string>).GetMethod("Add");

            // Preparing locals
            gen.DeclareLocal(currentType);  // dummyInstance (of the current type) - 0

            // Writing body

            // Create a dummy instance.  Use FormatterServices.GetUninitializedObject
            // so that the constructor doesn't actually run.
            // That way, authors of the class never experience us "magically" calling their constructors (with whatever side-effects that may have)
            // Note that we don't directly create an instance of the currentType which we are working on.  Instead,
            // we create an instance of its most derived known subclasss (at least, the most derived subclass known to our caller).
            // We do this so that we can map abstract classes, since the derived subclass is non-abstract (so we create is instead of the abstract one)
            // Note: in theory caller could construct this once, but that would require some refactoring of this method, which I haven't had time to do yet
                            gen.Emit(OpCodes.Ldtoken, mostDerivedKnownType);
                       gen.Emit(OpCodes.Call, getTypeFromHandleMethod);
                   gen.Emit(OpCodes.Call, getUninitializedObjectMethod);
                gen.Emit(OpCodes.Castclass, currentType);
            gen.Emit(OpCodes.Stloc_0);
           
            // for each field in it, get it offset then shove the result into the dictionary (dictionary of field names, keyed by offset)
            foreach (FieldInfo field in currentType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))                
            {
                // Generate the MSIL to get the offset for the given field. Here the indenting convention is that, for each call, the lines that put its arguments on the stack are indented one step relative to the line with the call
                // Note that this generated MSIL can access PRIVATE fields, simply because it is a Lightweight Code-gen method (and assuming appropriate permissions are granted)
                // See http://blogs.msdn.com/joelpob/archive/2004/03/31/105282.aspx 
                //and http://blogs.msdn.com/shawnfa/archive/2006/10/05/Using-Lightweight-CodeGen-from-Partial-Trust.aspx

                MethodInfo typedOffsetMethod = offsetMethod.MakeGenericMethod(field.FieldType);

                #region debug version
                /*        gen.Emit(OpCodes.Ldloc_0);   
                            gen.Emit(OpCodes.Ldloc_0);        
                        gen.Emit(OpCodes.Ldflda,   field);          
                    gen.Emit(OpCodes.Call,     typedOffsetMethod);
                gen.Emit(OpCodes.Call, writeInt);
                    gen.Emit(OpCodes.Ldstr,    field.Name);
                gen.Emit(OpCodes.Call, writeString); */
                #endregion


                    gen.Emit(OpCodes.Ldarg_0);                          // reminder to self, arg0 is the first param (not the "this" pointer) because we are making a static method
                        gen.Emit(OpCodes.Ldloc_0);   
                            gen.Emit(OpCodes.Ldloc_0);        
                        gen.Emit(OpCodes.Ldflda,   field);          
                    gen.Emit(OpCodes.Call,     typedOffsetMethod);
                    gen.Emit(OpCodes.Ldstr,    field.Name);
                gen.Emit(OpCodes.Call, addMethod);  
            }

            // done
            gen.Emit(OpCodes.Ret);

            return (Action<Dictionary<int, string>>)dynamicMethod.CreateDelegate(typeof(Action<Dictionary<int, string>>));
        }       

    }
}
